/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fjsei.yewu.flow;

import com.alibaba.fastjson2.JSON;
import com.querydsl.core.BooleanBuilder;
import graphql.relay.Connection;
import graphql.schema.DataFetchingEnvironment;
import io.camunda.zeebe.client.ZeebeClient;
import lombok.extern.slf4j.Slf4j;
import md.cm.geography.Adminunit;
import md.cm.unit.Unit;
import md.log.Opinion_Enum;
import md.specialEqp.*;
import md.specialEqp.inspect.*;
import md.system.Ifop_Enu;
import md.system.User;
import org.fjsei.yewu.aop.MetricsLogger;
import org.fjsei.yewu.bpm.AskForApprovalService;
import org.fjsei.yewu.bpm.IncidentProcessListService;
import org.fjsei.yewu.exception.CommonGraphQLException;
import org.fjsei.yewu.graphql.DbPageConnection;
import org.fjsei.yewu.graphql.IdMapper;
import org.fjsei.yewu.input.AgreementInput;
import org.fjsei.yewu.input.RequestInput;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.payload.RequestCudResp;
import org.fjsei.yewu.pojo.jsn.OpinionMemo;
import org.fjsei.yewu.pojo.sei.InspDeviceDat;
import org.fjsei.yewu.pojo.sei.TaskDetailAssDat;
import org.fjsei.yewu.resolver.Comngrql;
import org.fjsei.yewu.util.Tool;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.execution.BatchLoaderRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertTrue;

/**通用申请单的 graphQL接口
 * */
@Slf4j
@ConditionalOnBean(ZeebeClient.class)
@Controller
public class RequestController extends Comngrql implements IdMapper<Request> {
	@PersistenceContext(unitName = "entityManagerFactorySei")
	private EntityManager emSei;                //EntityManager相当于hibernate.Session：
	private final Requests requests;
	private final AskForApprovalService askForApprovalService;
	private final IncidentProcessListService incidentProcessListService;

	/** registry参数：IDEA报红却能运行成功，实际运行注入DefaultBatchLoaderRegistry;
     *  消除报红，测试平台Units没有报红 https://blog.csdn.net/zy103118/article/details/84523452
	 * 添加了 @ComponentScan({"md","org.fjsei.yewu"}) 以后: Units报红消除了。
	 * */
	public RequestController(BatchLoaderRegistry registry, Requests requests, AskForApprovalService askForApprovalService,
                             IncidentProcessListService incidentProcessListService) {
		this.requests = requests;
		this.askForApprovalService = askForApprovalService;
		this.incidentProcessListService = incidentProcessListService;
	}


	/**协议申请单的新建，修改，删除 :复合模式接口。
	 * 申请单提交也是用Ifop_Enu:UPD,
	 * 【zeebe终端被挂住】导致这里连续多次点击的，可能导致产生多个的流程实例。@Transactional也无法阻挡zeebe提交啊，DB互斥失败回滚zeebe也不能退回。
	 * opt=ADD情况下：假如inp.getDevs()为空但是task不是空的，可以直接把task.detail_list.isp.eqp所有设备台账都默认作为inp.getDevs()代为输入。
	 * */
	@MutationMapping
	@Transactional
	public RequestCudResp cudRequest(@Argument String id, @Argument Ifop_Enu opt, @Argument RequestInput inp) {
		RequestCudResp resp=new RequestCudResp();
		User user=checkAuth();
		assertNotNull(user, () -> "请登录");
		Request request=entityOf(id,Request.class);
		if(!Ifop_Enu.ADD.equals(opt)) {
			assertNotNull(request, () -> "没协议申请单");
			if (RequestSta_Enum.FIN == request.getStatus()) {
				assertTrue(user.equals(request.getDirector()), () -> "责任人才能做");
				if(RequestSta_Enum.END==inp.getStatus()){
			//todo 结束协议
				}else if(RequestSta_Enum.CANCEL==inp.getStatus()){
			//todo 取消协议
				}
			}
		}
//		List<Eqp> detsTodo = new ArrayList<>();
//		if(!Ifop_Enu.DEL.equals(opt) && null!=inp.getDevs())
//			inp.getDevs().forEach(item -> {
//				Eqp eqp=entityOf(item,Eqp.class);
//				assertNotNull(eqp, () -> "没该设备"+item);
//				detsTodo.add(eqp);
//			});
//		assertTrue(!detsTodo.isEmpty(), () -> "没待处理业务单");
//		Unit servu=fromInputUnitGlobalID(inp.getServu());
		Unit ispu=fromInputUnitGlobalID(inp.getIspu());
		User promoter=entityOf(inp.getDirector(),User.class);
		User auditor=entityOf(inp.getAuditor(),User.class);
		User dispatcher=entityOf(inp.getManager(),User.class);
//		Adminunit adminunit=entityOf(inp.getAd(),Adminunit.class);
		Request.RequestBuilder  requestBld=null;
		if(Ifop_Enu.UPD.equals(opt))
			requestBld= request.toBuilder();
		else if(Ifop_Enu.ADD.equals(opt))
			requestBld= Request.builder();
		if(!Ifop_Enu.DEL.equals(opt)) {
			//JAVA原生的assert #assert只能在debug（调试）中使用，而非rerelsease（正式）中
			assert requestBld != null;			//正常ADD时: inp.getDat()应没有。
			requestBld = requestBld.dat(inp.getDat())
					.mdtime(LocalDateTime.now());
			//只能修改，不能清空旧的设置的；
			requestBld =null!=auditor? requestBld.auditor(auditor) : requestBld;
			requestBld =null!=dispatcher? requestBld.manager(dispatcher) : requestBld;
			requestBld =null!=promoter? requestBld.director(promoter) : requestBld;
		}
		if(Ifop_Enu.ADD.equals(opt)) {
			assertTrue(StringUtils.hasText(inp.getMod()), () -> "mod空");
			if(inp.getMod().equals("stopInspec")){			//非正常耦合@前端代码： mod='暂停监检申请'
				Task task = entityOf(inp.getTask(),Task.class);
				Assert.isTrue(task != null,"未找到task:"+inp.getTask());
				assertTrue(user.equals(task.getLiabler()), () -> "责任人才能干");
				assertTrue(TaskState_Enum.PERSON.equals(task.getStatus()) || TaskState_Enum.DISP.equals(task.getStatus()), () -> "任务状态错");
				TaskDetailAssDat taskDat=new TaskDetailAssDat();
				taskDat.setTaskId(inp.getTask());
				AtomicBoolean setted = new AtomicBoolean(false);
				List<InspDeviceDat> devs= new ArrayList<>();
				task.getDets().forEach(det -> {
					if(null==det.getIsp())	 return;
					Eqp eqp=det.getIsp().getDev();
					if(null==eqp)	 return;
					InspDeviceDat inspD= InspDeviceDat.builder().id(eqp.getId(null)).titl(eqp.getTitl()).model(eqp.getModel())
							.fno(eqp.getFno()).address(eqp.getAddress()).build();
					devs.add(inspD);
					assertNotNull(eqp.getUseu(), () -> "没使用单位,cod="+eqp.getCod()+",oid="+eqp.getOid());
					if(!setted.get()){
						taskDat.set使用单(eqp.getUseu().name());
						taskDat.set设备种(eqp.getType());
						setted.set(true);
					}
				});
				taskDat.setDevs(devs);
				Assert.isTrue(devs.size()>0,"没监检设备");
				assertNotNull(task.getServu(), () -> "没服务单位");
				//快照task服务单位名，设备使用单位名，设备种类名，动态修改？免前端需要多次查询关联数据:前端还需要隐含保存此类字段(#信赖前端用户不瞎改掉@)
				taskDat.set服务单(task.getServu().name());
				String strJson = JSON.toJSONString(taskDat);
				requestBld =requestBld.dat(strJson);
				ispu= task.getIspu();
			}
			else if(inp.getMod().equals("taskDelay") || inp.getMod().equals("cancelTask"))
			{
				Task task = entityOf(inp.getTask(),Task.class);
				Assert.isTrue(task != null,"未找到task:"+inp.getTask());
				assertTrue(user.equals(task.getLiabler()), () -> "责任人才能干");
				assertTrue(TaskState_Enum.PERSON.equals(task.getStatus()) || TaskState_Enum.DISP.equals(task.getStatus()), () -> "任务状态错");
				TaskDetailAssDat taskDat=new TaskDetailAssDat();
				taskDat.setTaskId(inp.getTask());
				String strJson = JSON.toJSONString(taskDat);
				requestBld =requestBld.dat(strJson);
				ispu= task.getIspu();		//默认值依据任务配置，不看申请单的选定值
			}
//			assertTrue(TaskState_Enum.INIT.equals(task.getStatus()) || TaskState_Enum.DEPART.equals(task.getStatus()) || TaskState_Enum.OFFICE.equals(task.getStatus()), () -> "状态不支持拆分");
			request= requestBld.crman(user).ispu(ispu).mod(inp.getMod()).build();
//			Task finalNewTask = newTask;
//			detsTodo.forEach(item -> {
//				item.setTask(finalNewTask);
//			});
			requests.save(request);
//			task.getDets().removeAll(detsTodo);	 	//需要 ？当前接口就刷新。
		}
		else if(Ifop_Enu.UPD.equals(opt)) {
			//检查各阶段修改授权：
			if (RequestSta_Enum.INIT == request.getStatus())
				assertTrue(user.equals(request.getCrman()), () -> "本人才能做");
			else if(RequestSta_Enum.SUBMIT == request.getStatus())
				assertTrue(user.equals(request.getCrman()) || user.equals(request.getDirector()), () -> "非授权节点");
			else if(RequestSta_Enum.CHECK == request.getStatus())
				assertTrue(user.equals(request.getAuditor()) || user.equals(request.getManager()), () -> "非授权节点");
			else if(RequestSta_Enum.FIN == request.getStatus())
				throw new CommonGraphQLException("签名中不能改", id);
			assertTrue(RequestSta_Enum.FIN != request.getStatus(), () -> "已签订不能改");
			assertTrue(RequestSta_Enum.END != request.getStatus(), () -> "已结束不能改");
				//throw new CommonGraphQLException("已结束不能改", id); 不如上面简洁;

			if(RequestSta_Enum.SUBMIT==inp.getStatus()) {		//前端"提交申请"命令也走这个接口!
				//todo: 废除
				//作废已经生效的协议？需要另起炉灶还是直接替换？最好Agreement实体新建但是任务报告可以重新关联，首先断开旧协议关联，替换关联ID。
			}
			request= requestBld.build();
			//校验划转任务的任务容受性。同一个受检验单位？ JPA实体代理?不能直接比较？ assertTrue(newTask.getServu().equals(task.getServu())会报错！
//			assertTrue(newTask.getServu().getId().equals(task.getServu().getId()), () -> "非同一受检验单位");
//			Task finalNewTask1 = newTask;
//			detsTodo.forEach(item -> {
//				item.setTask(finalNewTask1);
//			});
//			newTask.getDets().addAll(detsTodo);
//			task.getDets().removeAll(detsTodo);
			requests.save(request);
		}
		else {		 //if(Ifop_Enu.DEL.equals(opt))   删除接口！
			//权限检查 or 审批？ 特别地放入日志。 todo？提交流转后直接作废？未提交的主动早早删除的。
//            INIT : 申请创建者有权删除。
//            END  ：没有人有权删除，只能后台维护删除。协议要删除的？有权限取消协议附属的任务？(协议取消还是已经协议完成执行终结的，不能随意删计费依据)
//            CANCEL ：申请创建者||最后责任人有权删除。
//            SUBMIT或其它状态：：其它状态都不能删除, 需要流程推进到CANCEL后再考虑删除。
			if(RequestSta_Enum.INIT == request.getStatus()){
                assertTrue(user.equals(request.getCrman()), () -> "创建者才能做");
            }
            else if(RequestSta_Enum.CANCEL == request.getStatus()){
                assertTrue(user.equals(request.getDirector()) || user.equals(request.getCrman()), () -> "责任人或创建者才能做");
				//申请创建者: 若是客户本人的，没有权做取消协议已关联任务的，实际可能无法成功删除协议。
            }else
				throw new CommonGraphQLException("没权删除或状态不允许", id);

            if(null!=request.getPrId()){
				if(!incidentProcessListService.canDeleteProcessInstance(request.getPrId()))
					throw new CommonGraphQLException("流程状态不允许删", id);
            }

//			List<Isp>  ispsTodo = new ArrayList<>();
//			detsTodo.forEach(det -> {
//				if(null==det.getIsp())	 return;
//				assertTrue(det.getIsp().getReps().isEmpty(), () -> "关联报告未清除,detId:"+det.getId());
//				ispsTodo.add(det.getIsp());	  	//永久库清理
//			});
//			isps.deleteAll(ispsTodo);
//			details.deleteAll(detsTodo);
//			task.getDets().removeAll(detsTodo);

			//直接删除报错：foreign key constraint: is still referenced from table 'task'; @需要是事前确保没有被别人关联到: cancellationTask接口可删。谁有权删除?
			requests.delete(request);
		}
		resp.setMe(request);
		return resp;
	}

	/**通用申请单的 查询：
	 * 注意QRequest qm照抄QAgreement qm = QAgreement.agreement;编译没毛病，只有过滤运行时才会报错antlr; sql竟能能乱关联；
	 * 小心：参数类型RequestInput被替换为AgreementInput和模型文件定义不一致的，竟然也不会报错。
	 * */
	@MetricsLogger
	@QueryMapping
	public Connection<Request> findRequestFilter(@Argument String orderBy, @Argument Boolean asc, @Argument RequestInput where, @Argument Integer first,
											  @Argument String after, @Argument Integer last, @Argument String before, DataFetchingEnvironment env) {
		User user=checkAuth();
		assertNotNull(user, () -> "请登录");
		DbPageConnection<Request> connection=new DbPageConnection(env);
		int offset=connection.getOffset();
		int limit=connection.getLimit();
		//【问题】这里排序date排序ASC后的，若没有添加id做辅助排序字段，导致多次执行从数据库读的分页数据很可能是重复的，CRDB返回实际记录的顺序不一定固定。不唯一的排序组合问题。
		//前端Relay列表看出：有些loadMore()可能并没有增加新实体，重复被自动归并了,导致队列长度也没增加；浪费通信数据。
		Pageable pageable;
		if (!StringUtils.hasLength(orderBy))
			pageable = PageOffsetFirst.of(offset, limit);
		else {
			pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy, "id"));
		}
		QRequest qm = QRequest.request;
		BooleanBuilder builder = new BooleanBuilder();
		//部门 科室,特别附带输入的： 针对的是auditor 最后一关口的审核人？ #通用申请单意义太过泛泛而谈了，如何认定？
		if(StringUtils.hasText(where.getDep()))
			builder.and(qm.auditor.dep.id.eq(Tool.fromGluuId(where.getDep()).getId()));
		if(StringUtils.hasText(where.getOffice()))
			builder.and(qm.auditor.office.id.eq(Tool.fromGluuId(where.getOffice()).getId()));
		if(StringUtils.hasText(where.getAuditor()))
			builder.and(qm.auditor.id.eq(Tool.fromGluuId(where.getAuditor()).getId()));
		if(StringUtils.hasText(where.getManager()))
			builder.and(qm.manager.id.eq(Tool.fromGluuId(where.getManager()).getId()));
		if(StringUtils.hasText(where.getDirector()))
			builder.and(qm.director.id.eq(Tool.fromGluuId(where.getDirector()).getId()));
		if(StringUtils.hasText(where.getCrman()))
			builder.and(qm.crman.id.eq(Tool.fromGluuId(where.getCrman()).getId()));
		if(null!=where.getDate1())
			builder.and(qm.crDate.goe(where.getDate1()));
		if(null!=where.getDate2())
			builder.and(qm.crDate.loe(where.getDate2()));
		if(null!=where.getQdate1())
			builder.and(qm.mdtime.goe(where.getQdate1().atStartOfDay()));
		if(null!=where.getQdate2())
			builder.and(qm.mdtime.loe(where.getQdate2().plusDays(1).atStartOfDay()));	   //0点（不包含这一天）
		if(null!=where.getStatusx() && where.getStatusx().size()>0)
			builder.and(qm.status.in(where.getStatusx()));
        if(StringUtils.hasText(where.getMod()))
			builder.and(qm.mod.eq(where.getMod()));
		if(StringUtils.hasText(where.getIspu())) {
			Unit unit=fromInputUnitGlobalID(where.getIspu());
			Assert.notNull(unit,"未找到Unit:"+where.getIspu());
			builder.and(qm.ispu.id.eq(unit.getId()));
		}
//		if(Boolean.TRUE.equals(where.getMecrman()))
//			builder.and(qm.crman.id.eq(user.getId()));
		Slice<Request> rpage= (Slice<Request>)requests.findAll(builder,pageable);
		List<Request> list=(List<Request>) rpage.toList();
		return connection.setListData(list).get(env);
	}

	/**申请单:首次提交流转，以及以后BPMN全流程的流转。
	 *【考虑】本接口复合做法：流转同时前端选定责任人，【耦合】根据流程图节点认定输入用途，顺带指定一个任务负责人：不一定要求zeebe体现运行计算收费子流程，服务端需要任务负责人。
	 * 前端根据流程用户任务节点名字关系定位：合适指定任务责任人，通过接口附带属性区分到底接口选定的是什么人角色。
	 * 前端输入的userTaskId有可能并不是操作者的用户任务！必须验证身份。
	 * memo改成 json data{ 意见： 描述, 签字日期：流转日期。  扩展??的细节追加的字段。}, 签名：省略，每一个层级审核人只能修改本层级本人data,上一级的数据下一级审核人也不能篡改;
	 * days改成 下一关超时天数或者时移交监察平台后的Manual Task反馈最大天数(超期需要分批提取？默认结束)。
	 * 最多三级别的审核流程： 1,2,3层级的审核人: level=1直接领导, level=2主管领导, level=3 最后最终批准人。
	 * men: 顺利转给下一级审核人的。回退时null; 配合level参数决定到底是哪一个 角色性质的审核人?
	 * 通用申请单在创建者提交时刻，不一定能够把后续（3层级的）2个的审核人全部配置好了，一层负责下一层级别的审核人选择。
	 * 正式Init提交和流转共同用本接口的： userTaskId=null是准备提交流程的。
	 * 【问题和解决办法】 @Transactional加CockroachDB 导致数据库本处理函数会重复运行的，意味着本函数里面掺杂非该数据库的其他操作必须提供幂等性，流程引擎触发等等代码也会多次执行的。
	 * 解决思路：@MutationMapping专门给非单纯一个数据库操作的就像zeebe流转操作这样的，专门搞出接口函数，里面杜绝对数据库增删改操作，也杜绝加@Transactional。
	 * 作为补充，流程BPMN图上面必须在每一个UserTask节点后面紧跟个ServiceTask来专门接替做数据库操作，两个流程图节点需要依赖输出参数来传递信息，苦了流程引擎要多存储传递接口输入的数据。
	 * level：正常不是第一次提交没必要这个参数。流程实例启动之后想要修改level关卡数的，需要提供而且只能在BPMN对应重新提交节点上流转时刻输入才有用的，前端默认节点数，修改后的节点数状态保存存储在业务dat{level1or3:?}隐含参数中算了,通常也不用配这个level。
	 * 【特别注意】本接口level照旧定义，非必须：第一次创建流程和变更level才需要提供；但是其它流转情况的，level须是null。
	 * @! 给前端数据一致性考量带来很麻烦，流转接口不能立刻修改数据库，需要稍等zeebe来修订数据库关联实体,前端何时才适合获取实体数据更新显示。点击扭转成功之后前端需要把实体标记为等待更新==#脏数据。
	 * @return 简单说明id; 异常直接报错。
	 * 注意流程引擎报告deadline exceeded超时的(太忙了),实际可能发动实例实际成功了哦。
	 *  */
	@MutationMapping
	public String  flowRequestTo(@Argument String entId, @Argument String userTaskId, @Argument Opinion_Enum allow, @Argument String data,@Argument String men,
						  @Argument Integer level, @Argument Integer days, @Argument String uri,@Argument String topic,@Argument String bpmn) {
		User user=checkAuth();
		User nextMan = entityOf(men,User.class);	 //提交给谁处理:
		Request request=entityOf(entId,Request.class);
		assertNotNull(request, () -> "没找到申请单:"+entId);
		boolean existActiveProcess= null != request.getPrId() && incidentProcessListService.isProcessActiveOk(request.getPrId());
		//【启动流程实例】还没有启动流程实例的情况:
		if(!existActiveProcess && (null == request.getStatus() || RequestSta_Enum.INIT == request.getStatus()) ) {
			//【奇怪】缓存机制可能导致的，手动修改数据库表的，没通知hibernate的缓存集群，导致了短时间内getPrId()读到并非是数据库最新的！不同步啊。
			assertTrue(null == request.getPrId(), () -> "流程id非空！");
			String  enId=request.getId(null);		//需要给外部系统 GlobalID
//			String  uriInit="/request/"+enId+"/all";
			String zeebeDf= StringUtils.hasText(bpmn)? bpmn : "nmRequest";    //允许不同的流程定义也能用：
			assertTrue("nmRequest".equals(zeebeDf), () -> "流程定义错");		  //启动新流程实例；一般就一个的flowDef ID,最新版本的bpmn。
			Long processId = askForApprovalService.startRequestFlow(zeebeDf,topic,enId,uri,nextMan,level);
			//关键的Process Instance Id，@@要避免遗忘掉已经生成的流程！zeebe引擎存储的遗漏占用;
			assertTrue(null != processId, () -> "流程开启失败");
			log.info("开动申请单, 这次的暂时跟踪不做保存的WWavax:prId：{}", processId);
//			request.setPrId(processId);
//			request.setStatus(RequestSta_Enum.SUBMIT);	  	//假如回退到了编制阶段，再修改申请单:退回==》INIT?
//			if(3==level){		  	//启动就配置多少级别 几次审批要走: level="3":最简化1层级审批的。
//				request.setAuditor(nextMan);		 //流程引擎zeebe存储和后端数据库存储协调性不够，需要各自存储，找寻流转历史记录。
//			}
//			else if(2==level) {		//两层级别申请审批模式, 最开始提交给谁
//				request.setManager(nextMan);
//			}
//			else if(1==level) {		//三层级别申请审批模式, 最开始提交给谁
//				request.setDirector(nextMan);
//			}
//			requests.save(request);
			//强制进入开启流程步骤。
			return "新实例:"+processId;	 	//没有 rUserTaskId
		}
		//旧的流程实例情况，从【待办事项】进入的：
		//??直接从zeebe服务提取 流程变量：统一流程变量ent 代表挂接的申请单或报告的实体表ID: GlobalID;
		//应当把关联的Report报告id给我传递进来啊，写入流程日志实体表也得用它。普通申请单的实体模型对象也有ID。
		assertTrue(StringUtils.hasText(userTaskId), () -> "须从UserTask进入");
		io.camunda.tasklist.dto.Task userTask= askForApprovalService.getFlownode(userTaskId);
		assertTrue(user.getUsername().equals(userTask.getAssignee()), () -> "不是自己的待办");
		//if(!user.getUsername().equals(userTask.getAssignee()))	return "不是自己的待办"; 可随意替别人流转。 @ zeebe安全接入??
		//【原本】有毛病做法：这里前端给的data直接在这地方保存到物理数据库的。现在改成经过zeebe引擎转手后再触发保存数据库，避免数据库@Transactional重做引起重复提交zeebe集群去:极端情况那以确保逻辑顺序正常。
		String flnode=userTask.getTaskDefinitionId();	   //当前用户任务节点：
		if(!"request_author_modify".equals(flnode) ) {
			level=null;		 //只能在重新修改再次提交的节点才能够搞变更level的，其它情况无效！
		}
		String memo=null;
		if(StringUtils.hasText(data)) {
			OpinionMemo opinion = JSON.parseObject(data, OpinionMemo.class);    //不管何种意见:data必须有的
			memo = opinion.getMemo();                //对应流程图上面say变量：允许有附加的除了memo其余字段:具体用处前端决定。
		}
		//最后审批人批准通过的，需要设置参数 action +days?
		//【问题】数据库事务重做的，导致zeebe实际做完没法撤销流程引擎已经流转到了下一个节点了，错误！
		//杜绝这里做 requests.save(request);
//		String action=request.getMod();   参数desLevel:修改目标的bpmn审批关口数。
		String rUserTaskId= askForApprovalService.flowRequestTo(userTaskId, request, allow, memo,days, nextMan,uri,data,level);
		return  "流程:"+rUserTaskId;
	}
}

